home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / system-config-printer / monitor.py < prev    next >
Text File  |  2009-10-19  |  28KB  |  734 lines

  1. #!/usr/bin/env python
  2.  
  3. ## Copyright (C) 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
  4. ## Copyright (C) 2007, 2008, 2009 Red Hat, Inc.
  5.  
  6. ## This program is free software; you can redistribute it and/or modify
  7. ## it under the terms of the GNU General Public License as published by
  8. ## the Free Software Foundation; either version 2 of the License, or
  9. ## (at your option) any later version.
  10.  
  11. ## This program is distributed in the hope that it will be useful,
  12. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. ## GNU General Public License for more details.
  15.  
  16. ## You should have received a copy of the GNU General Public License
  17. ## along with this program; if not, write to the Free Software
  18. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  19.  
  20. import cups
  21. cups.require("1.9.42")
  22. import dbus
  23. import dbus.glib
  24. import gobject
  25. import time
  26. from debug import *
  27. import pprint
  28.  
  29. global _
  30. _ = lambda x: x
  31. def set_gettext_function (x):
  32.     _ = x
  33. import statereason
  34. from statereason import StateReason
  35. statereason.set_gettext_function (_)
  36.  
  37. CONNECTING_TIMEOUT = 60 # seconds
  38. MIN_REFRESH_INTERVAL = 1 # seconds
  39.  
  40. def state_reason_is_harmless (reason):
  41.     if (reason.startswith ("moving-to-paused") or
  42.         reason.startswith ("paused") or
  43.         reason.startswith ("shutdown") or
  44.         reason.startswith ("stopping") or
  45.         reason.startswith ("stopped-partly")):
  46.         return True
  47.     return False
  48.  
  49. def collect_printer_state_reasons (connection):
  50.     result = {}
  51.     try:
  52.         printers = connection.getPrinters ()
  53.     except cups.IPPError:
  54.         return result
  55.  
  56.     for name, printer in printers.iteritems ():
  57.         reasons = printer["printer-state-reasons"]
  58.         for reason in reasons:
  59.             if reason == "none":
  60.                 break
  61.             if state_reason_is_harmless (reason):
  62.                 continue
  63.             if not result.has_key (name):
  64.                 result[name] = []
  65.             result[name].append (StateReason (name, reason))
  66.     return result
  67.  
  68. class Watcher:
  69.     # Interface definition
  70.     def monitor_exited (self, monitor):
  71.         debugprint (repr (monitor) + " exited")
  72.  
  73.     def state_reason_added (self, monitor, reason):
  74.         debugprint (repr (monitor) + ": +" + repr (reason))
  75.  
  76.     def state_reason_removed (self, monitor, reason):
  77.         debugprint (repr (monitor) + ": -" + repr (reason))
  78.  
  79.     def still_connecting (self, monitor, reason):
  80.         debugprint (repr (monitor) + ": `%s' still connecting" %
  81.                     reason.get_printer ())
  82.  
  83.     def now_connected (self, monitor, printer):
  84.         debugprint (repr (monitor) + ": `%s' now connected" % printer)
  85.  
  86.     def current_printers_and_jobs (self, monitor, printers, jobs):
  87.         debugprint (repr (monitor) + ": printers and jobs lists provided")
  88.  
  89.     def job_added (self, monitor, jobid, eventname, event, jobdata):
  90.         debugprint (repr (monitor) + ": job %d added" % jobid)
  91.  
  92.     def job_event (self, monitor, jobid, eventname, event, jobdata):
  93.         debugprint (repr (monitor) + ": job %d has event `%s'" %
  94.                     (jobid, eventname))
  95.  
  96.     def job_removed (self, monitor, jobid, eventname, event):
  97.         debugprint (repr (monitor) + ": job %d removed" % jobid)
  98.  
  99.     def printer_added (self, monitor, printer):
  100.         debugprint (repr (monitor) + ": printer `%s' added" % printer)
  101.  
  102.     def printer_event (self, monitor, printer, eventname, event):
  103.         debugprint (repr (monitor) + ": printer `%s' has event `%s'" %
  104.                     (printer, eventname))
  105.  
  106.     def printer_removed (self, monitor, printer):
  107.         debugprint (repr (monitor) + ": printer `%s' removed" % printer)
  108.  
  109.     def cups_connection_error (self, monitor):
  110.         debugprint (repr (monitor) + ": CUPS connection error")
  111.  
  112.     def cups_ipp_error (self, monitor, e, m):
  113.         debugprint (repr (monitor) + ": CUPS IPP error (%d, %s)" %
  114.                     (e, repr (m)))
  115.  
  116. class Monitor:
  117.     # Monitor jobs and printers.
  118.     DBUS_PATH="/com/redhat/PrinterSpooler"
  119.     DBUS_IFACE="com.redhat.PrinterSpooler"
  120.  
  121.     def __init__(self, watcher, bus=None, my_jobs=True, specific_dests=None,
  122.                  monitor_jobs=True, host=None, port=None, encryption=None):
  123.         self.watcher = watcher
  124.         self.my_jobs = my_jobs
  125.         self.specific_dests = specific_dests
  126.         self.monitor_jobs = monitor_jobs
  127.         self.jobs = {}
  128.         self.printer_state_reasons = {}
  129.         self.printers = set()
  130.         self.process_pending_events = True
  131.         self.fetch_jobs_timer = None
  132.  
  133.         if host:
  134.             cups.setServer (host)
  135.         if port:
  136.             cups.setPort (port)
  137.         if encryption:
  138.             cups.setEncryption (encryption)
  139.         self.user = cups.getUser ()
  140.         self.host = cups.getServer ()
  141.         self.port = cups.getPort ()
  142.         self.encryption = cups.getEncryption ()
  143.  
  144.         self.which_jobs = "not-completed"
  145.         self.reasons_seen = {}
  146.         self.connecting_timers = {}
  147.         self.still_connecting = set()
  148.         self.connecting_to_device = {}
  149.         self.received_any_dbus_signals = False
  150.         self.update_timer = None
  151.  
  152.         if bus == None:
  153.             try:
  154.                 bus = dbus.SystemBus ()
  155.             except dbus.exceptions.DBusException:
  156.                 # System bus not running.
  157.                 pass
  158.  
  159.         if bus != None:
  160.             bus.add_signal_receiver (self.handle_dbus_signal,
  161.                                      path=self.DBUS_PATH,
  162.                                      dbus_interface=self.DBUS_IFACE)
  163.             self.bus = bus
  164.  
  165.         self.sub_id = -1
  166.         self.refresh ()
  167.  
  168.     def get_jobs (self):
  169.         return self.jobs.copy ()
  170.  
  171.     def cleanup (self):
  172.         if self.sub_id != -1:
  173.             user = cups.getUser ()
  174.             try:
  175.                 cups.setUser (self.user)
  176.                 c = cups.Connection (host=self.host,
  177.                                      port=self.port,
  178.                                      encryption=self.encryption)
  179.                 c.cancelSubscription (self.sub_id)
  180.                 debugprint ("Canceled subscription %d" % self.sub_id)
  181.             except:
  182.                 pass
  183.             cups.setUser (user)
  184.  
  185.         if self.bus != None:
  186.             self.bus.remove_signal_receiver (self.handle_dbus_signal,
  187.                                              path=self.DBUS_PATH,
  188.                                              dbus_interface=self.DBUS_IFACE)
  189.  
  190.         timers = self.connecting_timers.values ()
  191.         for timer in [self.update_timer, self.fetch_jobs_timer]:
  192.             if timer:
  193.                 timers.append (timer)
  194.         for timer in timers:
  195.             gobject.source_remove (timer)
  196.  
  197.         self.watcher.monitor_exited (self)
  198.  
  199.     def set_process_pending (self, whether):
  200.         self.process_pending_events = whether
  201.  
  202.     def check_still_connecting(self, printer):
  203.         """Timer callback to check on connecting-to-device reasons."""
  204.         if not self.process_pending_events:
  205.             # Defer the timer by setting a new one.
  206.             timer = gobject.timeout_add (200, self.check_still_connecting,
  207.                                          printer)
  208.             self.connecting_timers[printer] = timer
  209.             return False
  210.  
  211.         del self.connecting_timers[printer]
  212.         debugprint ("Still-connecting timer fired for `%s'" % printer)
  213.         (printer_jobs, my_printers) = self.sort_jobs_by_printer ()
  214.         self.update_connecting_devices (printer_jobs)
  215.  
  216.         # Don't run this callback again.
  217.         return False
  218.  
  219.     def update_connecting_devices(self, printer_jobs={}):
  220.         """Updates connecting_to_device dict and still_connecting set."""
  221.         time_now = time.time ()
  222.         connecting_to_device = {}
  223.         trouble = False
  224.         for printer, reasons in self.printer_state_reasons.iteritems ():
  225.             connected = True
  226.             for reason in reasons:
  227.                 if reason.get_reason () == "connecting-to-device":
  228.                     have_processing_job = False
  229.                     for job, data in \
  230.                             printer_jobs.get (printer, {}).iteritems ():
  231.                         state = data.get ('job-state',
  232.                                           cups.IPP_JOB_CANCELED)
  233.                         if state == cups.IPP_JOB_PROCESSING:
  234.                             have_processing_job = True
  235.                             break
  236.  
  237.                     if not have_processing_job:
  238.                         debugprint ("Ignoring stale connecting-to-device x")
  239.                         continue
  240.  
  241.                     # Build a new connecting_to_device dict.  If our existing
  242.                     # dict already has an entry for this printer, use that.
  243.                     printer = reason.get_printer ()
  244.                     t = self.connecting_to_device.get (printer, time_now)
  245.                     connecting_to_device[printer] = t
  246.                     debugprint ("Connecting time: %d" % (time_now - t))
  247.                     if time_now - t >= CONNECTING_TIMEOUT:
  248.                         if have_processing_job:
  249.                             if printer not in self.still_connecting:
  250.                                 self.still_connecting.add (printer)
  251.                                 self.watcher.still_connecting (self, reason)
  252.                             if self.connecting_timers.has_key (printer):
  253.                                 gobject.source_remove (self.connecting_timers
  254.                                                        [printer])
  255.                                 del self.connecting_timers[printer]
  256.                                 debugprint ("Stopped connecting timer "
  257.                                             "for `%s'" % printer)
  258.  
  259.                     connected = False
  260.                     break
  261.  
  262.             if connected and self.connecting_timers.has_key (printer):
  263.                 gobject.source_remove (self.connecting_timers[printer])
  264.                 del self.connecting_timers[printer]
  265.                 debugprint ("Stopped connecting timer for `%s'" % printer)
  266.  
  267.         # Clear any previously-notified errors that are now fine.
  268.         remove = set()
  269.         for printer in self.still_connecting:
  270.             if not connecting_to_device.has_key (printer):
  271.                 remove.add (printer)
  272.                 self.watcher.now_connected (self, printer)
  273.                 if self.connecting_timers.has_key (printer):
  274.                     gobject.source_remove (self.connecting_timers[printer])
  275.                     del self.connecting_timers[printer]
  276.                     debugprint ("Stopped connecting timer for `%s'" % printer)
  277.  
  278.         self.still_connecting = self.still_connecting.difference (remove)
  279.         self.connecting_to_device = connecting_to_device
  280.  
  281.     def check_state_reasons(self, my_printers=set(), printer_jobs={}):
  282.         # Look for any new reasons since we last checked.
  283.         old_reasons_seen_keys = self.reasons_seen.keys ()
  284.         reasons_now = set()
  285.         for printer, reasons in self.printer_state_reasons.iteritems ():
  286.             for reason in reasons:
  287.                 tuple = reason.get_tuple ()
  288.                 printer = reason.get_printer ()
  289.                 reasons_now.add (tuple)
  290.                 if not self.reasons_seen.has_key (tuple):
  291.                     # New reason.
  292.                     self.watcher.state_reason_added (self, reason)
  293.                     self.reasons_seen[tuple] = reason
  294.  
  295.                 if (reason.get_reason () == "connecting-to-device" and
  296.                     not self.connecting_to_device.has_key (printer)):
  297.                     # First time we've seen this.
  298.  
  299.                     have_processing_job = False
  300.                     for job, data in \
  301.                             printer_jobs.get (printer, {}).iteritems ():
  302.                         state = data.get ('job-state',
  303.                                           cups.IPP_JOB_CANCELED)
  304.                         if state == cups.IPP_JOB_PROCESSING:
  305.                             have_processing_job = True
  306.                             break
  307.  
  308.                     if have_processing_job:
  309.                         t = gobject.timeout_add ((1 + CONNECTING_TIMEOUT)
  310.                                                  * 1000,
  311.                                                  self.check_still_connecting,
  312.                                                  printer)
  313.                         self.connecting_timers[printer] = t
  314.                         debugprint ("Start connecting timer for `%s'" %
  315.                                     printer)
  316.                     else:
  317.                         # Don't notify about this, as it must be stale.
  318.                         debugprint ("Ignoring stale connecting-to-device")
  319.                         if get_debugging ():
  320.                             debugprint (pprint.pformat (printer_jobs))
  321.  
  322.         self.update_connecting_devices (printer_jobs)
  323.         items = self.reasons_seen.keys ()
  324.         for tuple in items:
  325.             if not tuple in reasons_now:
  326.                 # Reason no longer present.
  327.                 reason = self.reasons_seen[tuple]
  328.                 del self.reasons_seen[tuple]
  329.                 self.watcher.state_reason_removed (self, reason)
  330.  
  331.     def get_notifications(self):
  332.         if not self.process_pending_events:
  333.             # Defer the timer callback.
  334.             if self.update_timer:
  335.                 gobject.source_remove (self.update_timer)
  336.  
  337.             self.update_timer = gobject.timeout_add (200,
  338.                                                      self.get_notifications)
  339.             return False
  340.  
  341.         debugprint ("get_notifications")
  342.         user = cups.getUser ()
  343.         try:
  344.             cups.setUser (self.user)
  345.             c = cups.Connection (host=self.host,
  346.                                  port=self.port,
  347.                                  encryption=self.encryption)
  348.  
  349.             try:
  350.                 try:
  351.                     notifications = c.getNotifications ([self.sub_id],
  352.                                                         [self.sub_seq + 1])
  353.                 except AttributeError:
  354.                     notifications = c.getNotifications ([self.sub_id])
  355.             except cups.IPPError, (e, m):
  356.                 cups.setUser (user)
  357.                 if e == cups.IPP_NOT_FOUND:
  358.                     # Subscription lease has expired.
  359.                     self.sub_id = -1
  360.                     self.refresh ()
  361.                     return False
  362.  
  363.                 self.watcher.cups_ipp_error (self, e, m)
  364.                 return True
  365.         except RuntimeError:
  366.             cups.setUser (user)
  367.             self.watcher.cups_connection_error (self)
  368.             return True
  369.  
  370.         cups.setUser (user)
  371.         deferred_calls = []
  372.         jobs = self.jobs.copy ()
  373.         for event in notifications['events']:
  374.             seq = event['notify-sequence-number']
  375.             self.sub_seq = seq
  376.             nse = event['notify-subscribed-event']
  377.             debugprint ("%d %s %s" % (seq, nse, event['notify-text']))
  378.             if get_debugging ():
  379.                 debugprint (pprint.pformat (event))
  380.             if nse.startswith ('printer-'):
  381.                 # Printer events
  382.                 name = event['printer-name']
  383.                 if nse == 'printer-added' and name not in self.printers:
  384.                     self.printers.add (name)
  385.                     deferred_calls.append ((self.watcher.printer_added,
  386.                                             (self, name)))
  387.  
  388.                 elif nse == 'printer-deleted' and name in self.printers:
  389.                     self.printers.remove (name)
  390.                     items = self.reasons_seen.keys ()
  391.                     for tuple in items:
  392.                         if tuple[1] == name:
  393.                             reason = self.reasons_seen[tuple]
  394.                             del self.reasons_seen[tuple]
  395.                             deferred_calls.append ((self.watcher.state_reason_removed,
  396.                                                     (self, reason)))
  397.                             
  398.                     if self.printer_state_reasons.has_key (name):
  399.                         del self.printer_state_reasons[name]
  400.  
  401.                     deferred_calls.append ((self.watcher.printer_removed,
  402.                                             (self, name)))
  403.                 elif name in self.printers:
  404.                     printer_state_reasons = event['printer-state-reasons']
  405.                     reasons = []
  406.                     for reason in printer_state_reasons:
  407.                         if reason == "none":
  408.                             break
  409.                         if state_reason_is_harmless (reason):
  410.                             continue
  411.                         reasons.append (StateReason (name, reason))
  412.                     self.printer_state_reasons[name] = reasons
  413.  
  414.                     deferred_calls.append ((self.watcher.printer_event,
  415.                                             (self, name, nse, event)))
  416.                 continue
  417.  
  418.             # Job events
  419.             jobid = event['notify-job-id']
  420.             if (nse == 'job-created' or
  421.                 (nse == 'job-state-changed' and
  422.                  not jobs.has_key (jobid) and
  423.                  event['job-state'] == cups.IPP_JOB_PROCESSING)):
  424.                 if (self.specific_dests != None and
  425.                     event['printer-name'] not in self.specific_dests):
  426.                     continue
  427.  
  428.                 try:
  429.                     attrs = c.getJobAttributes (jobid)
  430.                     if (self.my_jobs and
  431.                         attrs['job-originating-user-name'] != cups.getUser ()):
  432.                         continue
  433.  
  434.                     jobs[jobid] = attrs
  435.                 except AttributeError:
  436.                     jobs[jobid] = {'job-k-octets': 0}
  437.                 except cups.IPPError, (e, m):
  438.                     self.watcher.cups_ipp_error (self, e, m)
  439.                     jobs[jobid] = {'job-k-octets': 0}
  440.  
  441.                 deferred_calls.append ((self.watcher.job_added,
  442.                                         (self, jobid, nse, event,
  443.                                          jobs[jobid].copy ())))
  444.             elif (nse == 'job-completed' or
  445.                   (nse == 'job-state-changed' and
  446.                    event['job-state'] == cups.IPP_JOB_COMPLETED)):
  447.                 if not (self.which_jobs in ['completed', 'all']):
  448.                     try:
  449.                         del jobs[jobid]
  450.                         deferred_calls.append ((self.watcher.job_removed,
  451.                                                 (self, jobid, nse, event)))
  452.                     except KeyError:
  453.                         pass
  454.                     continue
  455.  
  456.             try:
  457.                 job = jobs[jobid]
  458.             except KeyError:
  459.                 continue
  460.  
  461.             for attribute in ['job-state',
  462.                               'job-name']:
  463.                 job[attribute] = event[attribute]
  464.             if event.has_key ('notify-printer-uri'):
  465.                 job['job-printer-uri'] = event['notify-printer-uri']
  466.  
  467.             deferred_calls.append ((self.watcher.job_event,
  468.                                    (self, jobid, nse, event, job.copy ())))
  469.  
  470.         self.set_process_pending (False)
  471.         self.update_jobs (jobs)
  472.         self.jobs = jobs
  473.  
  474.         for (fn, args) in deferred_calls:
  475.             fn (*args)
  476.         self.set_process_pending (True)
  477.  
  478.         # Update again when we're told to.  If we're getting CUPS
  479.         # D-Bus signals, however, rely on those instead.
  480.         if not self.received_any_dbus_signals:
  481.             if self.update_timer:
  482.                 gobject.source_remove (self.update_timer)
  483.  
  484.             interval = 1000 * notifications['notify-get-interval']
  485.             self.update_timer = gobject.timeout_add (interval,
  486.                                                      self.get_notifications)
  487.  
  488.         return False
  489.  
  490.     def refresh(self, which_jobs=None, refresh_all=True):
  491.         debugprint ("refresh")
  492.  
  493.         if which_jobs != None:
  494.             self.which_jobs = which_jobs
  495.  
  496.         user = cups.getUser ()
  497.         try:
  498.             cups.setUser (self.user)
  499.             c = cups.Connection (host=self.host,
  500.                                  port=self.port,
  501.                                  encryption=self.encryption)
  502.         except RuntimeError:
  503.             self.watcher.cups_connection_error (self)
  504.             cups.setUser (user)
  505.             return
  506.  
  507.         if self.sub_id != -1:
  508.             try:
  509.                 c.cancelSubscription (self.sub_id)
  510.             except cups.IPPError, (e, m):
  511.                 self.watcher.cups_ipp_error (self, e, m)
  512.  
  513.             if self.update_timer:
  514.                 gobject.source_remove (self.update_timer)
  515.  
  516.             debugprint ("Canceled subscription %d" % self.sub_id)
  517.  
  518.         try:
  519.             del self.sub_seq
  520.         except AttributeError:
  521.             pass
  522.  
  523.         events = ["printer-added",
  524.                   "printer-deleted",
  525.                   "printer-state-changed"]
  526.         if self.monitor_jobs:
  527.             events.extend (["job-created",
  528.                             "job-completed",
  529.                             "job-stopped",
  530.                             "job-state-changed"])
  531.  
  532.         try:
  533.             self.sub_id = c.createSubscription ("/", events=events)
  534.         except cups.IPPError, (e, m):
  535.             self.watcher.cups_ipp_error (self, e, m)
  536.  
  537.         cups.setUser (user)
  538.  
  539.         self.update_timer = gobject.timeout_add (MIN_REFRESH_INTERVAL * 1000,
  540.                                                  self.get_notifications)
  541.         debugprint ("Created subscription %d" % self.sub_id)
  542.  
  543.         if self.monitor_jobs:
  544.             jobs = self.jobs.copy ()
  545.             if self.which_jobs not in ['all', 'completed']:
  546.                 # Filter out completed jobs.
  547.                 filtered = {}
  548.                 for jobid, job in jobs.iteritems ():
  549.                     if job['job-state'] < cups.IPP_JOB_CANCELED:
  550.                         filtered[jobid] = job
  551.                 jobs = filtered
  552.  
  553.             self.fetch_first_job_id = 1
  554.             if self.fetch_jobs_timer:
  555.                 gobject.source_remove (self.fetch_jobs_timer)
  556.             self.fetch_jobs_timer = gobject.timeout_add (5, self.fetch_jobs,
  557.                                                          refresh_all)
  558.         else:
  559.             jobs = {}
  560.  
  561.         try:
  562.             self.printer_state_reasons = collect_printer_state_reasons (c)
  563.             dests = c.getPrinters ()
  564.             self.printers = set(dests.keys ())
  565.         except cups.IPPError, (e, m):
  566.             self.watcher.cups_ipp_error (self, e, m)
  567.             return
  568.         except RuntimeError:
  569.             self.watcher.cups_connection_error (self)
  570.             return
  571.  
  572.         if self.specific_dests != None:
  573.             for jobid in jobs.keys ():
  574.                 uri = jobs[jobid].get('job-printer-uri', '/')
  575.                 i = uri.rfind ('/')
  576.                 printer = uri[i + 1:]
  577.                 if printer not in self.specific_dests:
  578.                     del jobs[jobid]
  579.  
  580.         self.set_process_pending (False)
  581.         self.watcher.current_printers_and_jobs (self, self.printers.copy (),
  582.                                                 jobs.copy ())
  583.         self.update_jobs (jobs)
  584.         self.jobs = jobs
  585.         self.set_process_pending (True)
  586.         return False
  587.  
  588.     def fetch_jobs (self, refresh_all):
  589.         if not self.process_pending_events:
  590.             # Skip this call.  We'll get called again soon.
  591.             return True
  592.  
  593.         user = cups.getUser ()
  594.         try:
  595.             cups.setUser (self.user)
  596.             c = cups.Connection (host=self.host,
  597.                                  port=self.port,
  598.                                  encryption=self.encryption)
  599.         except RuntimeError:
  600.             self.watcher.cups_connection_error (self)
  601.             self.fetch_jobs_timer = None
  602.             cups.setUser (user)
  603.             return False
  604.  
  605.         limit = 1
  606.         try:
  607.             fetched = c.getJobs (which_jobs=self.which_jobs,
  608.                                  my_jobs=self.my_jobs,
  609.                                  first_job_id=self.fetch_first_job_id,
  610.                                  limit=limit)
  611.         except cups.IPPError, (e, m):
  612.             self.watcher.cups_ipp_error (self, e, m)
  613.             self.fetch_jobs_timer = None
  614.             cups.setUser (user)
  615.             return False
  616.  
  617.         cups.setUser (user)
  618.         got = len (fetched)
  619.         debugprint ("Got %s jobs, asked for %s" % (got, limit))
  620.  
  621.         deferred_calls = []
  622.         jobs = self.jobs.copy ()
  623.         jobids = fetched.keys ()
  624.         jobids.sort ()
  625.         if got > 0:
  626.             last_jobid = jobids[got - 1]
  627.         else:
  628.             last_jobid = self.fetch_first_job_id + limit
  629.         for jobid in xrange (self.fetch_first_job_id, last_jobid + 1):
  630.             try:
  631.                 job = fetched[jobid]
  632.                 if self.specific_dests != None:
  633.                     uri = job.get('job-printer-uri', '/')
  634.                     i = uri.rfind ('/')
  635.                     printer = uri[i + 1:]
  636.                     if printer not in self.specific_dests:
  637.                         raise KeyError
  638.  
  639.                 if jobs.has_key (jobid):
  640.                     fn = self.watcher.job_event
  641.                 else:
  642.                     fn = self.watcher.job_added
  643.  
  644.                 jobs[jobid] = job
  645.                 deferred_calls.append ((fn,
  646.                                         (self, jobid, '', {}, job.copy ())))
  647.             except KeyError:
  648.                 # No job by that ID.
  649.                 if jobs.has_key (jobid):
  650.                     del jobs[jobid]
  651.                     deferred_calls.append ((self.watcher.job_removed,
  652.                                             (self, jobid, '', {})))
  653.  
  654.         jobids = jobs.keys ()
  655.         jobids.sort ()
  656.         if got < limit:
  657.             trim = False
  658.             for i in range (len (jobids)):
  659.                 jobid = jobids[i]
  660.                 if not trim and jobid > last_jobid:
  661.                     trim = True
  662.             
  663.                 if trim:
  664.                     del jobs[jobid]
  665.                     deferred_calls.append ((self.watcher.job_removed,
  666.                                             (self, jobid, '', {})))
  667.  
  668.         self.update_jobs (jobs)
  669.         self.jobs = jobs
  670.  
  671.         for (fn, args) in deferred_calls:
  672.             fn (*args)
  673.  
  674.         if got < limit:
  675.             # That's all.  Don't run this timer again.
  676.             self.fetch_jobs_timer = None
  677.             return False
  678.  
  679.         # Remember where we got up to and run this timer again.
  680.         next = jobid + 1
  681.  
  682.         while not refresh_all and self.jobs.has_key (next):
  683.             next += 1
  684.  
  685.         self.fetch_first_job_id = next
  686.         return True
  687.  
  688.     def sort_jobs_by_printer (self, jobs=None):
  689.         if jobs == None:
  690.             jobs = self.jobs
  691.  
  692.         my_printers = set()
  693.         printer_jobs = {}
  694.         for job, data in jobs.iteritems ():
  695.             state = data.get ('job-state', cups.IPP_JOB_CANCELED)
  696.             if state >= cups.IPP_JOB_CANCELED:
  697.                 continue
  698.             uri = data.get ('job-printer-uri', '')
  699.             i = uri.rfind ('/')
  700.             if i == -1:
  701.                 continue
  702.             printer = uri[i + 1:]
  703.             my_printers.add (printer)
  704.             if not printer_jobs.has_key (printer):
  705.                 printer_jobs[printer] = {}
  706.             printer_jobs[printer][job] = data
  707.  
  708.         return (printer_jobs, my_printers)
  709.  
  710.     def update_jobs(self, jobs):
  711.         debugprint ("update_jobs")
  712.         (printer_jobs, my_printers) = self.sort_jobs_by_printer (jobs)
  713.         self.check_state_reasons (my_printers, printer_jobs)
  714.  
  715.     def update(self):
  716.         if self.update_timer:
  717.             gobject.source_remove (self.update_timer)
  718.  
  719.         self.update_timer = gobject.timeout_add (200, self.get_notifications)
  720.  
  721.     def handle_dbus_signal(self, *args):
  722.         self.update ()
  723.         if not self.received_any_dbus_signals:
  724.             self.received_any_dbus_signals = True
  725.  
  726. if __name__ == '__main__':
  727.     set_debugging (True)
  728.     m = Monitor (Watcher ())
  729.     loop = gobject.MainLoop ()
  730.     try:
  731.         loop.run ()
  732.     finally:
  733.         m.cleanup ()
  734.